package com.nowcoder.community.util;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

@Slf4j
public class SensitiveWordFilter {

    static {
        REPLACEMENT = "***";
        root = new TrieNode(' ', false);
        init();
    }

    private static final String REPLACEMENT;

    private static int keywordsCount = 0;

    private static class TrieNode {
        private final boolean isSensitive;

        private final char aChar;

        private final Map<Character, TrieNode> subNodes = new HashMap<>();

        TrieNode(char c, boolean isSensitive) {
            this.aChar = c;
            this.isSensitive = isSensitive;
        }

        public void addSubNode(TrieNode node) {
            this.subNodes.put(node.aChar, node);
        }

        private TrieNode getSubNode(char c) {
            return subNodes.get(c);
        }

        public boolean isSensitive() {
            return isSensitive;
        }
    }

    private static final TrieNode root;

    private static void init(){
        try(
                BufferedReader reader = CommunityUtils.getResourceFileReader("sensitive-words.txt")
        ) {
            if (reader != null) {
                Stream<String> lines = reader.lines();
                lines.forEach(SensitiveWordFilter::addKeyword);
                log.info(String.format("共加载了%s条敏感词...", keywordsCount));
            }
        } catch (UncheckedIOException | IOException e) {
            log.error("加载敏感词文件失败: " + e.getMessage());
        }

    }

    private static boolean isSymbol(Character c) {
        // Ox2E80 ~ Ox9FFF 属于东亚文字之外的字符.
        return c != null && (c < 0x2E80 || c > 0X9FFF);
    }

    private static void addKeyword(String keyword) {
        TrieNode pNode = root;
        if (keyword != null) {
            for (int i = 0; i < keyword.length(); i++) {
                char c = keyword.charAt(i);
                TrieNode node = pNode.getSubNode(c);
                if (node == null) {
                    pNode.addSubNode(node = new TrieNode(c, i == keyword.length() - 1));
                }
                pNode = node;
            }
            keywordsCount++;
        }
    }

    public static String filter(String text) {
        if (text == null || text.length() == 0) {
            return text;
        }
        TrieNode pNode = root;
        int begin = 0;
        int position = 0;
        StringBuilder sb = new StringBuilder();
        while (position < text.length()) {
            char c = text.charAt(position);
            if (isSymbol(c)) {
                if (pNode == root) {
                    sb.append(c);
                    begin++;
                }
                position++;
                continue;
            }
            pNode = pNode.getSubNode(c);
            if (pNode == null) {
                sb.append(c);
                position = ++begin;
                pNode = root;
            } else if (pNode.isSensitive()){
                sb.append(REPLACEMENT);
                begin = ++position;
                pNode = root;
            } else {
                position++;
            }
        }

        sb.append(text.substring(begin));
        return sb.toString();
    }

    public static boolean hasSensitive(String text) {
        if (text == null || text.length() == 0) {
            return false;
        }
        TrieNode pNode = root;
        int begin = 0;
        int position = 0;
        while (position < text.length()) {
            char c = text.charAt(position);
            if (isSymbol(c)) {
                if (pNode == root) {
                    begin++;
                }
                position++;
                continue;
            }
            pNode = pNode.getSubNode(c);
            if (pNode == null) {
                position = ++begin;
                pNode = root;
            } else if (pNode.isSensitive()){
                return true;
            } else {
                position++;
            }
        }

        return false;
    }
}
